#--
# Copyright (c) 2007 Maximilian Ott
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#++
require 'markaby'
require_dependency 'wbuilder_show'
require_dependency 'wbuilder_edit'

module WBuilder
  class ActionViewTemplateHandler # :nodoc:
    def initialize(action_view)
      @action_view = action_view
    end

    def render(template, local_assigns, file_path)
      b = Builder.new(@action_view, template, local_assigns, file_path, binding)
      res = b.to_s
#     puts ">>> #{res}"
      res
    end
  end
  
  class Builder
    include Show
    include Edit
    
    @@default = {
      :indent => 0,
      :output_helpers => true,
      :output_xml_instruction => true,
      :output_meta_tag => true,
      :auto_validation => true,
      :tagset => Markaby::XHTMLTransitional.tagset,
      :root_attributes => {
        :xmlns => 'http://www.w3.org/1999/xhtml', :'xml:lang' => 'en', :lang => 'en'
      }
    }
    
    def initialize(action_view, template, local_assigns, file_path, xbinding)
      @action_view = action_view
      @assigns = action_view.assigns.dup
 
      @@default.each do |k, v|
        instance_variable_set("@#{k}", @assigns.delete(k) || v)
      end
      @assigns.each do |k, v|
#        puts "BUILDER> #{k} = #{v}"
        instance_variable_set("@#{k}", v)
      end

      @mode ||= :show
      
      @template = template
      @streams = [[]]      
      @content = XmlMarkup.new(self, :indent => @indent, :target => @streams.last)


#     @t = template; @l = local_assigns; @f = file_path; breakpoint
     
      unless local_assigns.empty?
        code = ""
        local_assigns.each do |k, v|
          code << "#{k} = local_assigns[:#{k}]\n"
#          puts "LOCAL ASSIGNMENT: #{k} = #{v}"
#        if v.kind_of? Builder
#          @object = v._object
#        end
        end
        eval code, binding
      end    
      @object, @object_name, @options, @mode, @count = Thread.current[:wbuilder] || [nil, nil, nil, nil, 0]
      
      eval(template, binding, file_path)      
    end
    
    # Returns a string containing the HTML stream.  Internally, the stream is stored as an Array.
    def to_s
      @streams.last.to_s
    end
    
    # Render a partial and include the result into this view.
    #
    def render(options)
#      puts "RENDER>>"  

      Thread.current[:wbuilder] = [@object, @object_name, @options, @mode, @count]
#      res = @action_view.render(options.reverse_merge(:object => self))
      # Allow helpers to directly access the markup stream.
      @action_view.instance_variable_set("@builder", self) 
      @action_view.instance_variable_set("@out", @content) 
      res = @action_view.render(options)
      text(res.to_s) if res
#      puts "RRRENDER >> #{res}"
      res
    end
    
    # Write a +string+ to the HTML stream without escaping it.
    def text(string)
      @content << string.to_s
      nil
    end
    alias_method :<<, :text
    alias_method :concat, :text

    # Captures the HTML code built inside the +block+.  This is done by creating a new
    # stream for the builder object, running the block and passing back its stream as a string.
    #
    #   >> capture { h1 "TEST"; h2 "CAPTURE ME" }
    #   => "<h1>TITLE</h1>\n<h2>CAPTURE ME</h2>\n"
    #
    def capture(&block)
      @streams.push(@content.target = [])
      @content.level += 1
      str = instance_eval(&block)
      str = @streams.last.join if @streams.last.any?
      @streams.pop
      @content.level -= 1
      @content.target = @streams.last
      str
    end

    # Allow for NO surounding tag. Primarily used if tag is decided
    # dynamically with the option of no tag.
    #
    # * args - are being ignored
    #
    def nothing(*args, &block)
      instance_eval(&block)
    end


    
    def method_missing(sym, *args, &block)
#      puts "BBB>> #{sym} : #{@action_view.respond_to?(sym, true)}"
      
      m = "_#{@mode}_#{sym}"
#      puts "MM>> Looking for '#{m}' '#{self.respond_to?(m)}'"      
      if self.respond_to?(m, true)
        self.send m, *args, &block
      elsif @action_view.respond_to?(sym)
#      @s = sym; breakpoint
#        puts "MM>> Calling '#{sym}' '#{@action_view.respond_to?(sym)}'"
        
#        @streams.push(@content.target = [])
#        @content.level += 1
#        res = @action_view.send sym, *args, &block
#        res = @streams.last.join if @streams.last.any?
#        @streams.pop
#        @content.level -= 1
#        @content.target = @streams.last

        # Allow helpers to directly access the markup stream.
        @action_view.instance_variable_set("@builder", self) 
        @action_view.instance_variable_set("@out", @content) 
        res = @action_view.send sym, *args, &block
#        puts "BUILDER:RES #{sym}>> #{res} >> #{@streams.last}"
        self.text(res) if res

      
#      if @helpers.respond_to?(sym, true) && !self.class.ignored_helpers.include?(sym)
#        r = @helpers.send(sym, *args, &block)
#        if @output_helpers and r.respond_to? :to_str
#          fragment { @builder << r }
#        else
#          r
#        end
      elsif @assigns.has_key?(sym)
        @assigns[sym]
      elsif @assigns.has_key?(stringy_key = sym.to_s)
        # Rails' ActionView assigns hash has string keys for
        # instance variables that are defined in the controller.
        @assigns[stringy_key]
      elsif instance_variables.include?(ivar = "@#{sym}")
        instance_variable_get(ivar)
#      elsif !@helpers.nil? && @helpers.instance_variables.include?(ivar)
#        @helpers.instance_variable_get(ivar)
#      elsif ::Builder::XmlMarkup.instance_methods.include?(sym.to_s) 
#        @content.__send__(sym, *args, &block)
#      elsif @tagset.nil?
#        res = tag!(sym, *args, &block)
#        puts "WBUILDER:TAG>> #{res}"
#        res
      elsif (attrs = @tagset[sym])
        @content.__send__(sym, *args, &block)   
      else
        puts "WBUILDER>> Missing '#{sym}'"
#        raise NoMethodError, "no such method `#{sym}'"
      end
    end
  
    ############## COMMON ##################

    def page_header(&block)
      @content.div(:class => 'page_header', &block)
    end

    def page_commands(&block) 
      @content.div(:class => 'page_header_links', &block)
    end
    
    def page_title(*args, &block)
      @content.h1(*args, &block)
    end
    
    def page_navigation(section, action, record_id = 'xx', &block)
#      puts "PAGE_NAV>>> #{block}"

      @page_nav_section = section
      @page_nav_action = action
      @page_nav_record_id = record_id
      @content.div(:class => 'page_nav') do
        @content.ul do
          block.call if block
        end
      end
    end
    
    def page_tab(name, human_name = name.to_s.capitalize)
      @content.li(:class => @page_nav_section == name ? 'selected' : '') do
        url = @action_view.url_for :action => "#{@page_nav_action}_#{name}", :id => @page_nav_record_id
        @content.a human_name, :href => url
        #image_tag 'arrow_down2', :class => 'arrow'
      end
    end
      
    def page_body(object_name, object, options, &block)
      raise ArgumentError, "Missing block '#{object_name}'" unless block_given?
      raise ArgumentError, "Missing object '#{object_name}'" if object.nil?

      @object_name = object_name
      @object = object
      @options = options
      @block = block
      @mode = options[:mode] || @mode || :show
      
      @content.div(:class => "page") do
        self.__send__ "_#{@mode}_page_body", object_name, object, options, &block
      end
    end
    
    # Execute block for every object in the list either provided
    # as second argument, or the one returned by sending +object_name+
    # to the current object.
    #
    def foreach(object_name, *args, &block)
      rec_array = args.first || @object.__send__(object_name)
      rec_array.each do |r|
        block.call(r)
      end
    end
    
    #
    # Add 'Show' abd 'hide' links which will toggle the content in the +block+
    #
    # Options:
    #   +label+ - Added to the 'show'/'hide' text in the toggle link
    #   +show_label+ - Override default show label - if nil don't show 'show' at all
    #   +hide_label+ - Override default hide label - if nil don't show 'hide' at all
    #   +show+ - Show block initially when set (hiding is default)
    #
    def hide_show(options = {}, &block)
      id = __unique_id
      show_id = id + 'show'
      hide_id = id + 'hide'
      div_id = id + 'div'
      label = options[:label] || ''
      label = ' ' + label unless label.empty?
      show =  options[:show] != nil
      
      show_label = (options.has_key? :show_label) ? options[:show_label] : "Show#{label}"
      hide_label = (options.has_key? :hide_label) ? options[:hide_label] : "Hide#{label}"      
      
      @content.span(:id => show_id, :style => show ? 'display:none' : '') do
        if show_label
          link_to_function(show_label, "$(\"#{div_id}\").show();$(\"#{hide_id}\").show();$(\"#{show_id}\").hide()", :class => :admin)
        end
      end
      @hide_func = "$(\"#{div_id}\").hide();$(\"#{hide_id}\").hide();$(\"#{show_id}\").show()"
      @content.span(:id => hide_id, :style => show ? '' : 'display:none') do
        if hide_label
          link_to_function(hide_label, @hide_func, :class => :admin)
        end
      end
      @content.div(:id => div_id, :style => show ? '' : 'display:none') do
        yield
      end
      nil
    end

    def missing(m = 'Missing', options = {})
      @content.span m, :class => options[:class] || :undefined
      nil
    end

    # Return the current context object
    def context
      @object
    end
    
    def _object
      @object
    end
    
    # sometimes the rails helper can't find us directly'
    def _erbout
      self
    end
    
    private 
    def __unique_id
      @count = @count ? @count + 1 : 1
      @id_prefix ||= "w#{object_id}_"
      @id_prefix + @count.to_s
    end
    
    
    
  end

  
  class XmlMarkup < ::Builder::XmlMarkup
    
#    class Foo
#      
#    end
    
    attr_accessor :target, :level
    
    def initialize(wbuilder, options={})
      @wbuilder = wbuilder
#      puts "111 XMLMARKUP  #{object_id} >>>>>>>> #{options.inspect}"
#      @o = options; breakpoint
#      @foo = Foo.new
      super options
    end
    
    def nothing(*args)
      yield self
    end
    
    def _nested_structures(block)
      @level += 1
#      puts "XMLMARKUP>>>>>>>> NESTED"
      block.call
    ensure
      @level -= 1
    end
    
    def method_missing(sym, *args, &block)
#      puts "XML (#{target!.object_id})>> #{sym} : #{args}"
#      @s = sym; breakpoint
      if args.length == 0 && block.nil?
#        super    
        self
      else
        super    
      end
    end

    
  end

  # Every tag method in Markaby returns a Fragment.  If any method gets called on the Fragment,
  # the tag is removed from the Markaby stream and given back as a string.  Usually the fragment
  # is never used, though, and the stream stays intact.
  #
  # For a more practical explanation, check out the README.
#  class Fragment < ::Builder::BlankSlate
#    
#    def initialize(*args)
#      @stream, @start, @length = args
#    end
#    
#    def method_missing(*args, &block)
#      # We can't do @stream.slice!(@start, @length),
#      # as it would invalidate the @starts and @lengths of other Fragment instances.
#      @str = @stream[@start, @length].to_s
#      @stream[@start, @length] = [nil] * @length
#      def self.method_missing(*args, &block)
#        @str.send(*args, &block)
#      end
#      @str.send(*args, &block)
#    end
#  end

end
